package com.cloudkinto.appletservice.user.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cloudkinto.appletservice.user.AppletUserService;
import com.cloudkinto.appletservice.user.vo.*;
import com.cloudkinto.common.common.CodeGenerator;
import com.cloudkinto.common.common.MD5Util;
import com.cloudkinto.common.constant.GlobalConstants;
import com.cloudkinto.common.constant.SysConstant;
import com.cloudkinto.common.error.ErrorEnum;
import com.cloudkinto.common.exception.BizExceptionI18;
import com.cloudkinto.common.utils.OkHttpUtils;
import com.cloudkinto.common.utils.OrderCodeUtil;
import com.cloudkinto.common.web.SingleResult;
import com.cloudkinto.dao.*;
import com.cloudkinto.entity.*;
import com.cloudkinto.service.email.MailSenderService;
import com.cloudkinto.service.file.FileService;
import com.cloudkinto.service.role.RoleService;
import com.cloudkinto.service.user.captchamode.CaptchaModeService;
import com.cloudkinto.service.user.captchamode.Context;
import com.cloudkinto.service.user.captchamode.impl.PhoneSendMsgImpl;
import com.cloudkinto.service.user.vo.UserDetailRes;
import com.cloudkinto.session.UserSession;
import com.cloudkinto.session.UserSessionManager;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * service实现类
 * </p>
 *
 * @author 张永远
 * @since 2020-05-27
 */
@Service
@Transactional(rollbackFor = Exception.class)//事务回滚
public class AppletUserServiceImpl extends ServiceImpl<UserDao, UserDo> implements AppletUserService {
    @Value("${gbc.login.verificationCode}")
    private boolean verificationCode;

    @Autowired
    private UserDao dao;
    @Autowired
    private FileService fileService;
    @Autowired
    private CompanyDao companyDao;
    @Autowired
    private FileDao fileDao;
    @Autowired
    private RoleDao roleDao;
    @Autowired
    private RoleService roleService;
    @Autowired
    private ShopDao shopDao;
    @Autowired
    private DicBusinessItemDao dicBusinessItemDao;
    @Autowired
    private UserSessionManager userSessionManager;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private MailSenderService mailSenderService;
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private WxUserDao wxUserDao;


/*    @Override
    public SingleResult sendVerificationCode(String telPhone) {
        LambdaQueryWrapper<UserDo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserDo::getUserPhone, telPhone)
                .eq(UserDo::getUserType, 1)
                .last("limit 1");
        UserDo userDo = dao.selectOne(wrapper);
        if (userDo == null) {
            throw new BizExceptionI18("no.user.phone");
        } else if (userDo.getUserStatus() != 1) {
            throw new BizExceptionI18("account.no.pass");
        }
        CaptchaModeService captchaModeService = new PhoneSendMsgImpl(userDo, redisTemplate);
        Context context = new Context(captchaModeService);
        context.contextSend(SysConstant.DELETE_FLAG_FALSE);
        String code = CodeGenerator.generate4Number();
        redisTemplate.opsForValue().set("phoneCode:" + userDo.getUserPhone(), code, 5, TimeUnit.MINUTES);
        String code2= (String) redisTemplate.opsForValue().get("phoneCode:"+ userDo.getUserPhone());
        return SingleResult.success("验证码发送成功，请及时查收", null);
    }*/
@Override
public SingleResult sendVerificationCode(String telPhone) {
    LambdaQueryWrapper<UserDo> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(UserDo::getUserPhone, telPhone)
            .eq(UserDo::getUserType, 1)
            .last("limit 1");
    UserDo userDo = dao.selectOne(wrapper);
    if (userDo == null) {
        throw new BizExceptionI18("no.user.phone");
    } else if (userDo.getUserStatus() != 1) {
        throw new BizExceptionI18("account.no.pass");
    }

    // 生成验证码
    String code = CodeGenerator.generate4Number();

    // 存储验证码到Redis
    redisTemplate.opsForValue().set("phoneCode:" + userDo.getUserPhone(), code, 5, TimeUnit.MINUTES);

    // 发送验证码
    CaptchaModeService captchaModeService = new PhoneSendMsgImpl(userDo, redisTemplate);
    Context context = new Context(captchaModeService);
  context.contextSend(SysConstant.DELETE_FLAG_FALSE);

    return SingleResult.success("验证码发送成功，请及时查收", null);
}


    @Override
    public AppletUserLoginSuccessRes login(AppletUserLoginReq req) {
        UserDo userDo = null;
        //校验验证码
        if (verificationCode && req.getType() == AppletUserLoginReq.LOGIN_TYPE.VER.getValue()) {
            //校验验证码
            String code = (String) redisTemplate.opsForValue().get("phoneCode:" + req.getUserAccount());
            LambdaQueryWrapper<UserDo> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(UserDo::getUserPhone, req.getUserAccount())
                    .eq(UserDo::getUserType, 1)
                    .last("limit 1");
            int count = dao.selectCount(wrapper);
            if (count < 1)             throw new BizExceptionI18("no.user.phone");
            if (StringUtils.isBlank(code) || !(code.equals(req.getVerificationCode()))) {
                throw new BizExceptionI18("verify.code.error.no.pass");
            } else if (!(code.equals(req.getVerificationCode()))) {
                throw new BizExceptionI18(".code.error");
            }
            userDo = this.baseMapper.selectOne(wrapper);
        } else {
            MD5Util md5 = new MD5Util(SysConstant.SALT_STRING, "MD5");
            LambdaQueryWrapper<UserDo> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(UserDo::getUserAccount, req.getUserAccount())
                    .eq(UserDo::getUserPassword, md5.encode(req.getUserPassword()))
                    .eq(UserDo::getUserRoleType,req.getUserRoleType())
                    .eq(UserDo::getUserType, 1);
            userDo = this.baseMapper.selectOne(wrapper);
            if (userDo == null) {
                throw new BizExceptionI18("no.account.info");
            } else if (!userDo.getUserPassword().equals(md5.encode(req.getUserPassword()))) {
                throw new BizExceptionI18("password.error");
            }
        }
        if (userDo == null) {
            throw new BizExceptionI18("no.account.info");
        } else if (userDo.getUserStatus() != 1) {
            throw new BizExceptionI18("account.checking");
        }
        LambdaQueryWrapper<WxUserDo> wxUserDoWrapper = new LambdaQueryWrapper<>();
        wxUserDoWrapper.eq(WxUserDo::getTelPhone, req.getUserAccount());
        wxUserDoWrapper.eq(WxUserDo::getUserType, SysConstant.DELETE_FLAG_TRUE);
        wxUserDoWrapper.eq(WxUserDo::getUserRoleType, req.getUserRoleType());
        WxUserDo wxUserDo = wxUserDao.selectOne(wxUserDoWrapper);
        if (wxUserDo == null) {
            wxUserDo = new WxUserDo();
            wxUserDo.setCompanyId(userDo.getCompanyId());
            wxUserDo.setUserId(userDo.getId());
            wxUserDo.setOpenid(req.getOpenId());
            wxUserDo.setUserType(SysConstant.DELETE_FLAG_TRUE);
            if (StringUtils.isBlank(req.getOpenId())) throw new BizExceptionI18("no.openId");
            String sessionKey = (String) redisTemplate.opsForValue().get("session_key:" + req.getOpenId());
            if (sessionKey == null) throw new BizExceptionI18("no.account.bind");
            wxUserDo.setSessionKey(sessionKey);
            wxUserDo.setTelPhone(userDo.getUserPhone());
            wxUserDao.insert(wxUserDo);
        }
        return creatLoginSuccessSession(userDo);
//        res.setMenuTree(roleService.getMenuTree(userDo.getRoleId(),1));
    }

  /*  @Override
    public AppletUserLoginSuccessRes login(AppletUserLoginReq req) {
        UserDo userDo = null;
        // 校验验证码
        if (verificationCode && req.getType() == AppletUserLoginReq.LOGIN_TYPE.VER.getValue()) {
            // 校验验证码
            String code = (String) redisTemplate.opsForValue().get("phoneCode:" + req.getUserAccount());
            LambdaQueryWrapper<UserDo> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(UserDo::getUserPhone, req.getUserAccount())
                    .eq(UserDo::getUserType, 1)
                    .eq(UserDo::getUserRoleType, req.getUserRoleType())
                    .last("limit 1");
            int count = dao.selectCount(wrapper);
            if (count < 1) throw new BizExceptionI18("no.user.phone");
            if (StringUtils.isBlank(code) || !(code.equals(req.getVerificationCode()))) {
                throw new BizExceptionI18("verify.code.error.no.pass");
            }
            userDo = this.baseMapper.selectOne(wrapper);
        } else {
            MD5Util md5 = new MD5Util(SysConstant.SALT_STRING, "MD5");
            LambdaQueryWrapper<UserDo> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(UserDo::getUserAccount, req.getUserAccount())
                    .eq(UserDo::getUserPassword, md5.encode(req.getUserPassword()))
                    .eq(UserDo::getUserRoleType, req.getUserRoleType())
                    .eq(UserDo::getUserType, 1);
            userDo = this.baseMapper.selectOne(wrapper);
            if (userDo == null) {
                throw new BizExceptionI18("no.account.info");
            } else if (!userDo.getUserPassword().equals(md5.encode(req.getUserPassword()))) {
                throw new BizExceptionI18("password.error");
            }
        }
        if (userDo == null) {
            throw new BizExceptionI18("no.account.info");
        } else if (userDo.getUserStatus() != 1) {
            throw new BizExceptionI18("account.checking");
        }

        // 验证 userRoleType 是否为后台用户
        if (req.getType() == 1 && !isBackendUser(userDo.getUserRoleType())) {
            throw new BizExceptionI18("no.backend.access");
        }

        LambdaQueryWrapper<WxUserDo> wxUserDoWrapper = new LambdaQueryWrapper<>();
        wxUserDoWrapper.eq(WxUserDo::getTelPhone, req.getUserAccount())
                .eq(WxUserDo::getUserType, SysConstant.DELETE_FLAG_TRUE)
                .eq(WxUserDo::getUserRoleType, req.getUserRoleType());
        WxUserDo wxUserDo = wxUserDao.selectOne(wxUserDoWrapper);
        if (wxUserDo == null) {
            wxUserDo = new WxUserDo();
            wxUserDo.setCompanyId(userDo.getCompanyId());
            wxUserDo.setUserId(userDo.getId());
            wxUserDo.setOpenid(req.getOpenId());
            wxUserDo.setUserType(SysConstant.DELETE_FLAG_TRUE);
            wxUserDo.setUserRoleType(req.getUserRoleType());
            if (StringUtils.isBlank(req.getOpenId())) throw new BizExceptionI18("no.openId");
            String sessionKey = (String) redisTemplate.opsForValue().get("session_key:" + req.getOpenId());
            if (sessionKey == null) throw new BizExceptionI18("no.account.bind");
            wxUserDo.setSessionKey(sessionKey);
            wxUserDo.setTelPhone(userDo.getUserPhone());
            wxUserDao.insert(wxUserDo);
        }
        return creatLoginSuccessSession(userDo);
    }*/

    private boolean isBackendUser(Integer userRoleType) {
        // 假设后台用户的 userRoleType 为 1
        return userRoleType == 1;
    }


    /**
     * 创建token和基本信息
     *
     * @param userDo
     * @return
     */
    private AppletUserLoginSuccessRes creatLoginSuccessSession(UserDo userDo) {

        // 创建会话
        //创建会话
        UserSession session = userSessionManager.createSession(userDo, true);
        AppletUserLoginSuccessRes res = new AppletUserLoginSuccessRes();
        res.setUserId(session.getUserId());
        res.setSessionId(session.getSessionId()); // 设置 Token
        res.setUserAccount(userDo.getUserAccount());
        res.setUserName(userDo.getUserName());
        res.setUserRoleType(userDo.getUserRoleType());
        res.setHeadUrl(fileService.getFilePath(userDo.getHeadFileId()));
        return res;
    }

    @Override
    public SingleResult getToken(String username, String password) {
        MD5Util md5 = new MD5Util(SysConstant.SALT_STRING, "MD5");

        LambdaQueryWrapper<UserDo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserDo::getUserAccount, username)
                .eq(UserDo::getUserPassword, md5.encode(password))
                .eq(UserDo::getUserType, 1);
        UserDo userDo = this.baseMapper.selectOne(wrapper);
        if (userDo == null) {
            throw new BizExceptionI18("account.password.error");
        } else if (userDo.getUserStatus() != 1) {
            throw new BizExceptionI18("account.checking");
        }

        //创建会话
        UserSession session = userSessionManager.createSession(userDo, true);
        return SingleResult.success(session.getSessionId());
    }

    @Override
    public void logout(LogOutReq logOutReq) {
        String token = logOutReq.getToken();
        userSessionManager.clearSession(token);
        Integer userRoleType = logOutReq.getUserRoleType();
        Long userId = logOutReq.getUserId();
        wxUserDao.deleteByLogOutReq(userId, userRoleType);

    }

    @Override
    public AppletUserRegisterReq register(AppletUserRegisterReq req) {
        checkRegister(req);

        if (req.getCompanyFileId() != null) {
            fileService.uploadFile(req.getCompanyFileId());
        }

        CompanyDo companyDo = new CompanyDo();
        companyDo.setName(req.getCompanyName());
        companyDo.setFileId(req.getCompanyFileId());
        companyDo.setCreateTime(new Date());
        companyDo.setDeleteFlag(SysConstant.DELETE_FLAG_FALSE);
        companyDao.insert(companyDo);
        companyDo.setCompanyCode(OrderCodeUtil.generateCompanyCode(companyDo.getId()));
        companyDao.updateById(companyDo);

        MD5Util md5 = new MD5Util(SysConstant.SALT_STRING, "MD5");

    /*    LambdaQueryWrapper<RoleDo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RoleDo::getDevCode, SysConstant.clientAdmin);
        RoleDo roleDo = roleDao.selectOne(wrapper);


        //注册用户默认指定 客户管理员角色
        if (roleDo != null) {
            userDo.setRoleId(roleDo.getId());
        }*/
        UserDo userDo = new UserDo();
        userDo.setUserPassword(md5.encode(req.getUserPassword()));
        userDo.setUserAccount(req.getUserAccount());
        userDo.setUserName(req.getUserName());
        userDo.setUserMail(req.getUserMail());
        userDo.setUserPhone(req.getUserPhone());
        userDo.setUserRoleType(req.getUserRoleType());
        userDo.setCompanyId(companyDo.getId());
        userDo.setUserStatus(0);
        userDo.setUserType(1);
        userDo.setCreateTime(new Date());
        userDo.setDeleteFlag(SysConstant.DELETE_FLAG_FALSE);
        dao.insert(userDo);
        threadPoolTaskExecutor.execute(() -> {
            mailSenderService.sendEmail("有新的客户注册", "名字：" + req.getUserName() + "<br />公司名称：" + req.getCompanyName() +
                            "<br />联系电话：" + req.getUserPhone() +
                            "<br />电子邮箱：" + req.getUserMail() + "<br />公司证件：" + fileService.getFilePath(req.getCompanyFileId()),
                    SysConstant.cloudkintEmail);
        });
        return req;
    }

    private void checkRegister(AppletUserRegisterReq req) {
        LambdaQueryWrapper<UserDo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserDo::getUserAccount, req.getUserAccount());
        wrapper.eq(UserDo::getUserRoleType,req.getUserRoleType());
        int count = this.baseMapper.selectCount(wrapper);
        if (count > 0) {
            throw new BizExceptionI18("existed.account.info");
        }
    }


    @Override
    public SingleResult appletAccountVerify(String code) {
        String body = OkHttpUtils.okHttpRequest("https://api.weixin.qq.com/sns/jscode2session?appid=" + GlobalConstants.APPLet_ID_WX +
                        "&secret=" + GlobalConstants.APPLet_Serect_WX + "&js_code=" + code + "&grant_type=authorization_code",
                HttpMethod.GET.name(), "");
        String openId = JSONObject.parseObject(body).getString("openid");
        String unionid = JSONObject.parseObject(body).getString("unionid");
        String sessionKey = JSONObject.parseObject(body).getString("session_key");
        LambdaQueryWrapper<WxUserDo> wxWrapper = new LambdaQueryWrapper<>();
        wxWrapper.eq(WxUserDo::getOpenid, openId);
        wxWrapper.eq(WxUserDo::getUserType, SysConstant.DELETE_FLAG_TRUE);
        List<WxUserDo> wxUserDos = wxUserDao.selectList(wxWrapper);
        WxUserDo wxUserDo =new WxUserDo();
        if(wxUserDos.size()>0)
        {
            wxUserDo=  wxUserDos.get(wxUserDos.size()-1);
            UserDo userDo = dao.selectById(wxUserDo.getUserId());
            AppletUserLoginSuccessRes res=new AppletUserLoginSuccessRes();
            if(userDo!=null) {
                 res = creatLoginSuccessSession(userDo);
            }
            res.setOpenId(openId);
            wxUserDo.setSessionKey(sessionKey);
            wxUserDo.setUnionid(unionid);
            wxUserDao.updateById(wxUserDo);
            res.setSessionKey(sessionKey);
            res.setUserRoleType(wxUserDo.getUserRoleType());
            return SingleResult.success("已绑定", res);
        }

       else {
            AppletUserLoginSuccessRes res = new AppletUserLoginSuccessRes();
            res.setOpenId(openId);
            res.setSessionKey(sessionKey);
            res.setUserRoleType(res.getUserRoleType());
            redisTemplate.opsForValue().set("session_key:" + openId, sessionKey, 15, TimeUnit.MINUTES);

            return SingleResult.failure(ErrorEnum.NO_BIND_ERROR.getResultCode(),ErrorEnum.NO_BIND_ERROR.getResultMsg() , res);
        }

    }

    @Override
    public String decryptData(AppletDecryptReq req) throws Exception {
        // 被加密的数据
        byte[] dataByte = new byte[0];
        dataByte = Base64.decode(req.getEncryptedData());
        String sessionKey = (String) redisTemplate.opsForValue().get("session_key:" + req.getOpenId());
        if (sessionKey == null) {
            LambdaQueryWrapper<WxUserDo> wrapper = new LambdaQueryWrapper();
            wrapper.eq(WxUserDo::getOpenid, req.getOpenId());
            wrapper.eq(WxUserDo::getUserType, SysConstant.DELETE_FLAG_TRUE);
            WxUserDo userDo = wxUserDao.selectOne(wrapper);
            if (userDo != null)
                sessionKey = userDo.getSessionKey();
        }
        if (sessionKey == null) throw new BizExceptionI18("session.key.error");
        // 加密秘钥
        byte[] keyByte = Base64.decode(sessionKey);
        // 偏移量
        byte[] ivByte = Base64.decode(req.getIv());
        // 如果密钥不足16位，那么就补足.  这个if 中的内容很重要
        int base = 16;
        if (keyByte.length % base != 0) {
            int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
            byte[] temp = new byte[groups * base];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
            keyByte = temp;
        }
        // 初始化
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
        AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
        parameters.init(new IvParameterSpec(ivByte));
        cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
        byte[] resultByte = cipher.doFinal(dataByte);
        if (null != resultByte && resultByte.length > 0) {
            String result = new String(resultByte, "UTF-8");
            return JSONObject.parseObject(result).toJSONString();
        }
        return null;
    }

    @Override
    public SingleResult detailInfo(Long id) {
        UserDo entityDo = dao.selectById(id);
        if (entityDo == null) {
            throw new BizExceptionI18("no.user.info");
        }
        UserDetailRes res = new UserDetailRes();

        res.setId(entityDo.getId());
        res.setUserAccount(entityDo.getUserAccount());
        res.setUserName(entityDo.getUserName());
        res.setUserMail(entityDo.getUserMail());
        res.setUserPhone(entityDo.getUserPhone());

        CompanyDo companyDo = companyDao.selectById(entityDo.getCompanyId());
        if (companyDo != null) {
            res.setCompanyName(companyDo.getName());
        }
        FileDo fileDo = fileDao.selectById(entityDo.getHeadFileId());
        if (fileDo != null) {
            res.setHeadFileUrl(fileService.getFilePath(fileDo.getId()));
            res.setHeadFileId(fileDo.getId());
            res.setFileName(fileDo.getFileName());
        }
        return SingleResult.success(res);
    }

    @Override
    public int updateUserInfoPhone(AppletUserUpdateReq req) {
        UserDo entityDo = dao.selectById(req.getId());
        entityDo.setUserPhone(req.getUserPhone());
//        handlerUpdateBefore(entityDo, userId);
        return dao.updateById(entityDo);
    }

    @Override
    public int updateUserInfoMail(AppletUserUpdateReq req) {
        UserDo entityDo = dao.selectById(req.getId());
        entityDo.setUserMail(req.getUserMail());
//        handlerUpdateBefore(entityDo, userId);
        return dao.updateById(entityDo);
    }

    @Override
    public int updateUserInfoName(AppletUserUpdateReq req) {
        UserDo entityDo = dao.selectById(req.getId());
        entityDo.setUserName(req.getUserName());
//        handlerUpdateBefore(entityDo, userId);
        return dao.updateById(entityDo);
    }

    @Override
    public int updatePassword(AppletUpdatePasswordReq req) {
        UserDo entityDo = dao.selectById(req.getUserId());
        MD5Util md5 = new MD5Util(SysConstant.SALT_STRING, "MD5");
        if (!(md5.encode(req.getOldPwd()).equals(entityDo.getUserPassword()))) {
            throw new BizExceptionI18("original.password.error");
        }
        entityDo.setUserPassword(md5.encode(req.getNewPwd()));
        return dao.updateById(entityDo);
    }
}

